﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Input.Control {
    /// <summary>
    /// Convienent class that sets up First Person Shooter (FPS) input controls where
    /// WSAD keys move the camera forward/backword/left/right and the mouse looks around.
    /// </summary>
    public class FirstPersonControl {
        private Camera _cam;
        private float _moveSpeed = 150.0f;
        private float _rotateSpeed = .1f;

        /// <summary>
        /// Gets or sets the camera the FPS controls will use.
        /// </summary>
        public Camera Camera {
            get {
                return _cam;
            }
            set {
                _cam = value;
            }
        }

        /// <summary>
        /// Gets or sets the movement speed of the camera. Default is 150.
        /// </summary>
        public float MoveSpeed {
            get {
                return _moveSpeed;
            }
            set {
                _moveSpeed = value;
            }
        }

        /// <summary>
        /// Gets or sets the camera rotation speed. Default is .1f.
        /// </summary>
        public float RotateSpeed {
            get {
                return _rotateSpeed;
            }
            set {
                _rotateSpeed = value;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FirstPersonControl"/> class.
        /// </summary>
        /// <param name="camera">The camera to use</param>
        /// <param name="inputLayer">The input layer that the controls will add input triggers to</param>
        public FirstPersonControl(Camera camera, InputLayer inputLayer) {
            _cam = camera;

            inputLayer.RegisterTrigger(new InputTrigger(new KeyPressedCondition(Keys.A, true),
                new InputAction(delegate(GameTime time) {
                    Move(-_cam.Right, time);
            })));
            inputLayer.RegisterTrigger(new InputTrigger(new KeyPressedCondition(Keys.D, true),
                new InputAction(delegate(GameTime time) {
                    Move(_cam.Right, time);
            })));
            inputLayer.RegisterTrigger(new InputTrigger(new KeyPressedCondition(Keys.W, true),
                new InputAction(delegate(GameTime time) {
                    Move(_cam.Direction, time);
            })));
            inputLayer.RegisterTrigger(new InputTrigger(new KeyPressedCondition(Keys.S, true),
                new InputAction(delegate(GameTime time) {
                    Move(-_cam.Direction, time);
            })));
            inputLayer.RegisterTrigger(new InputTrigger(new PassThroughCondition(),
                new InputAction(delegate(GameTime time) {
                    RotateCamera(time);
            })));
        }

        private void Move(Vector3 dir, GameTime time) {
            _cam.Position += dir * (float) time.ElapsedTimeInSeconds * _moveSpeed;
        }

        private void RotateCamera(GameTime time) {
            MouseState ms = Mouse.GetMouseState();
            float tpf = (float) time.ElapsedTimeInSeconds;
            int centerX = _cam.Viewport.Width / 2;
            int centerY = _cam.Viewport.Height / 2;
            float dx = centerX - ms.X;
            float dy = centerY - ms.Y;

            Matrix mat;
            Vector3 temp;
            if(dx != 0) {
                mat = Matrix.FromAngleAxis(MathHelper.ToRadians(_rotateSpeed * dx), Vector3.Up);
                temp = Vector3.Transform(_cam.Right, mat);
                _cam.Right = temp;
                temp = Vector3.Transform(_cam.Direction, mat);
                _cam.Direction = temp;
                temp = Vector3.Transform(_cam.Up, mat);
                _cam.Up = temp;
            }

            if(dy != 0) {
                mat = Matrix.FromAngleAxis(MathHelper.ToRadians(_rotateSpeed * dy), _cam.Right);
                temp = Vector3.Transform(_cam.Right, mat);
                _cam.Right = temp;
                temp = Vector3.Transform(_cam.Direction, mat);
                _cam.Direction = temp;
                temp = Vector3.Transform(_cam.Up, mat);
                _cam.Up = temp;
            }

            _cam.Up = Vector3.Normalize(_cam.Up);
            _cam.Direction = Vector3.Normalize(_cam.Direction);
            _cam.Right = Vector3.Normalize(_cam.Right);

            Mouse.SetPosition(centerX, centerY);
        }

        private class PassThroughCondition : InputCondition {
            public override bool Check(GameTime time) {
                return true;
            }
        }
    }
}
